package indi.zhifa.core.multidatasource.core;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
import com.baomidou.mybatisplus.autoconfigure.SqlSessionFactoryBeanCustomizer;
import indi.zhifa.core.multidatasource.core.factorbean.MultiDataSourceFactorBean;
import indi.zhifa.core.multidatasource.core.factorbean.SingleDataSourceFactorBean;
import indi.zhifa.core.multidatasource.core.factorbean.SqlSessionFactorBean;
import indi.zhifa.core.multidatasource.entity.SysDbEntity;
import indi.zhifa.core.multidatasource.enums.EDataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.type.TypeHandler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class MultiDataSourceConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware {

    private BeanFactory mBeanFactory;
    private ResourceLoader mResourceLoader;
    private Environment mEnvironment;


    private ApplicationContext mApplicationContext;

    static final String DATASOURCE_PREFIX = "datasource_";
    static final String SQL_SESSION_FACTORY_PREFIX = "sqlSessionFactory_";
    static final String SQL_SESSION_TEMPLATE_PREFIX = "sqlSessionTemplate_";

    RoutingDataSourceMemo mRoutingDataSourceMemo;

    public MultiDataSourceConfigurer(BeanFactory pBeanFactory,
                                     ResourceLoader pResourceLoader,
                                     Environment pEnvironment,
                                     RoutingDataSourceMemo pRoutingDataSourceMemo) {
        mBeanFactory = pBeanFactory;
        mResourceLoader = pResourceLoader;
        mEnvironment = pEnvironment;
        mRoutingDataSourceMemo = pRoutingDataSourceMemo;
    }


    @Override
    public void afterPropertiesSet() throws Exception {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        String profile = mEnvironment.getProperty("spring.profiles.active");
        String url = mEnvironment.getProperty("spring.datasource.url");
        String userName = mEnvironment.getProperty("spring.datasource.username");
        String password = mEnvironment.getProperty("spring.datasource.password");
        SingleConnectionDataSource singleConnectionDataSource = new SingleConnectionDataSource(
                url, userName,password,false);
        JdbcTemplate jdbcTemplate = new JdbcTemplate(singleConnectionDataSource);
        List<Map<String,Object>> res =  jdbcTemplate.queryForList("select * from sys_db where profile = ?",profile);
        List<SysDbEntity> sysDbEntityList = res.stream().map(m-> BeanUtil.toBean(m,SysDbEntity.class)).collect(Collectors.toList());
        Map<String,List<SysDbEntity>> sysDbEntityListMap = sysDbEntityList.stream().collect(Collectors.groupingBy(SysDbEntity::getDatasourceGroup));

        for(Map.Entry<String,List<SysDbEntity>> entry : sysDbEntityListMap.entrySet()){
            String datasourceGroup = entry.getKey();
            List<SysDbEntity> list = entry.getValue();
            SysDbEntity first = list.get(0);
            switch (first.getDatasourceType()){
                case 0 ->{
                    AbstractBeanDefinition singleDataSourceBeanDef = genSingleDataSourceFactorBean(first);
                    registry.registerBeanDefinition(DATASOURCE_PREFIX+datasourceGroup,singleDataSourceBeanDef);
                    AbstractBeanDefinition singleSqlSessionFactorBean = genSqlSessionFactorBean(EDataSourceType.SINGLE,datasourceGroup);
                    registry.registerBeanDefinition(SQL_SESSION_FACTORY_PREFIX+datasourceGroup,singleSqlSessionFactorBean);
                }
                case 1 ->{
                    AbstractBeanDefinition multiDataSourceBeanDef = genMultiDataSourceFactorBean(datasourceGroup,list,mRoutingDataSourceMemo);
                    registry.registerBeanDefinition(DATASOURCE_PREFIX+datasourceGroup,multiDataSourceBeanDef);

                    AbstractBeanDefinition multiSqlSessionFactorBean = genSqlSessionFactorBean(EDataSourceType.MULTI,datasourceGroup);
                    registry.registerBeanDefinition(SQL_SESSION_FACTORY_PREFIX+datasourceGroup,multiSqlSessionFactorBean);
                }
            }
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        mApplicationContext = applicationContext;
    }

    public AbstractBeanDefinition genSingleDataSourceFactorBean(SysDbEntity pSysDbEntity){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SingleDataSourceFactorBean.class);
        builder.addConstructorArgValue(pSysDbEntity);
        return builder.getBeanDefinition();
    }

    public AbstractBeanDefinition genMultiDataSourceFactorBean(String pKey,List<SysDbEntity> pSysDbEntityList,RoutingDataSourceMemo pRoutingDataSourceMemo){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MultiDataSourceFactorBean.class);
        builder.addConstructorArgValue(pKey);
        builder.addConstructorArgValue(pSysDbEntityList);
        builder.addConstructorArgValue(pRoutingDataSourceMemo);
        return builder.getBeanDefinition();
    }

    public AbstractBeanDefinition genSqlSessionFactorBean(EDataSourceType pDataSourceType, String pDataSourceName){
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactorBean.class);

        builder.addConstructorArgValue(pDataSourceType);
        builder.addConstructorArgReference(DATASOURCE_PREFIX + pDataSourceName);
        ObjectProvider<MybatisPlusProperties> mybatisPlusPropertiesProvider = mBeanFactory.getBeanProvider(MybatisPlusProperties.class);
        builder.addConstructorArgValue(mybatisPlusPropertiesProvider);
        builder.addConstructorArgValue(mResourceLoader);
        builder.addConstructorArgValue(mApplicationContext);

        ObjectProvider<Interceptor> interceptorsProvider = mBeanFactory.getBeanProvider(Interceptor.class);
        builder.addConstructorArgValue(interceptorsProvider);

        ObjectProvider<TypeHandler> typeHandlerProvider = mBeanFactory.getBeanProvider(TypeHandler.class);
        builder.addConstructorArgValue(typeHandlerProvider);

        ObjectProvider<LanguageDriver> languageDriverProvider = mBeanFactory.getBeanProvider(LanguageDriver.class);
        builder.addConstructorArgValue(languageDriverProvider);

        ObjectProvider<DatabaseIdProvider> databaseIdProviderProvider = mBeanFactory.getBeanProvider(DatabaseIdProvider.class);
        builder.addConstructorArgValue(databaseIdProviderProvider);

        ResolvableType configurationCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, ConfigurationCustomizer.class);
        ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizerListProvider = mBeanFactory.getBeanProvider(configurationCustomizerTargetType);
        builder.addConstructorArgValue(configurationCustomizerListProvider);

        ResolvableType sqlSessionFactoryBeanCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, SqlSessionFactoryBeanCustomizer.class);
        ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizerListProvider = mBeanFactory.getBeanProvider(sqlSessionFactoryBeanCustomizerTargetType);
        builder.addConstructorArgValue(sqlSessionFactoryBeanCustomizerListProvider);

        ResolvableType mybatisPlusPropertiesCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, MybatisPlusPropertiesCustomizer.class);
        ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider = mBeanFactory.getBeanProvider(mybatisPlusPropertiesCustomizerTargetType);
        builder.addConstructorArgValue(mybatisPlusPropertiesCustomizerProvider);

        // 显式添加配置类的依赖
        //builder.addDependsOn("mybatisPlusInterceptor");

        return builder.getBeanDefinition();
    }

}
